package com.licode.codekit.utils;

import org.junit.Test;
import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

public class PomFile {
    private static final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

    private String filePath;
    private Document rootDoc;

    private Node parentNode;
    private Node modulesNode;
    private Node properties;

    @Test
    public void test() {
        PomFile pomFile = new PomFile();
        pomFile.parse("d:\\pom.xml");
        pomFile.save("d:\\pom-1.xml");
    }

    private Node findValidNextSibling(Node node) {
        Node nextNode = node.getNextSibling();
        while (nextNode != null) {
            if (nextNode.getNodeType() == Node.ELEMENT_NODE) {
                break;
            }
            nextNode = nextNode.getNextSibling();
        }

        return nextNode;
    }

    private Node findNodeNext(Element root, String name) {
        Node node = findNodeForElement(root, name);
        return node == null ? null : findValidNextSibling(node);
    }

    private Node findNodeForElement(Element root, String name) {
        NodeList nodeList;
        nodeList = root.getElementsByTagName(name);
        if (nodeList.getLength() > 0) {
            return nodeList.item(nodeList.getLength() - 1);
        } else {
            return null;
        }
    }

    private Node findModulesInsertNode(Element root) {
        Node node = findNodeNext(root, "packaging");

        if (null == node) {
            node = findNodeForElement(root, "properties");
        }
        if (null == node) {
            node = findNodeForElement(root, "dependencies");
        }
        if (null == node) {
            node = findNodeNext(root, "description");
        }

        return node;
    }

    public boolean parse(String filePath) {
        try (FileInputStream fis = new FileInputStream(filePath)) {
            this.filePath = filePath;
            parse(fis);
        } catch (Exception e) {
            e.printStackTrace();
            clear();
        }

        return isValid();
    }

    public boolean parse(InputStream inputStream) {
        try {
            rootDoc = dbf.newDocumentBuilder().parse(inputStream);
            this.initData();
        } catch (Exception e) {
            e.printStackTrace();
            clear();
        }

        return isValid();
    }

    public boolean isValid() {
        return null != rootDoc;
    }

    private void clear() {
        rootDoc = null;
        modulesNode = null;
        parentNode = null;
    }

    private Element insertNode(Node root, Node beforeNode, String name) {
        Element element = rootDoc.createElement(name);
        if (beforeNode == null) {
            root.appendChild(element);
        } else {
            root.insertBefore(element, beforeNode);
        }

        return element;
    }

    private Element appendNode(Node root, String name, String val) {
        Element element = rootDoc.createElement(name);
        element.setTextContent(val);
        root.appendChild(element);
        return element;
    }

    private void removeBlackBlank() {
        XmlUtil.removeBlankNodes(rootDoc.getDocumentElement());
    }

    public void setParentGroupId(String groupId) {
        updateNodeValue(parentNode, "groupId", groupId);
    }

    public void setParentArtifactId(String artifactId) {
        updateNodeValue(parentNode, "artifactId", artifactId);
    }

    public void setParentVersion(String version) {
        updateNodeValue(parentNode, "version", version);
    }

    public void setGroupId(String groupId) {
        updateNodeValue(getRootNode(), "groupId", groupId);
    }

    public void setArtifactId(String artifactId) {
        updateNodeValue(getRootNode(), "artifactId", artifactId);
    }

    public void setVersion(String version) {
        updateNodeValue(getRootNode(), "version", version);
    }

    public void setModuleName(String name) {
        updateNodeValue(getRootNode(), "name", name);
    }

    public void setPackaging(String packaging) {
        updateNodeValue(getRootNode(), "packaging", packaging);
    }

    public void normalizeGroupId() {
        if (getParentGroupId().equals(getGroupId())) {
            Node node = XmlUtil.getNode(getRootNode(), "groupId");
            if (node != null) {
                getRootNode().removeChild(node);
            }
        }
    }

    public String getParentGroupId() {
        return XmlUtil.getNodeValue(parentNode, "groupId", "");
    }

    public String getParentArtifactId() {
        return XmlUtil.getNodeValue(parentNode, "artifactId", "");
    }

    public String getParentVersion() {
        return XmlUtil.getNodeValue(parentNode, "version", "");
    }

    public String getGroupId() {
        return XmlUtil.getNodeValue(getRootNode(), "groupId", "");
    }

    public int getJdkVersion() {
        if (null == this.properties) {
            return 8;
        }
        String jdkVer = XmlUtil.getNodeValue(this.properties, "java.version", "1.8");
        if ("1.8".equals(jdkVer)) {
            return 8;
        } else {
            try {
                return Integer.parseInt(jdkVer);
            } catch (Exception ignore) {
                return 8;
            }
        }
    }

    public String getArtifactId() {
        return XmlUtil.getNodeValue(getRootNode(), "artifactId", "");
    }

    public String getVersion(String defVal) {
        return XmlUtil.getNodeValue(getRootNode(), "version", defVal);
    }

    public String getModuleName(String defVal) {
        return XmlUtil.getNodeValue(getRootNode(), "name", defVal);
    }

    public String getPackaging() {
        return XmlUtil.getNodeValue(getRootNode(), "packaging", "");
    }

    public Set<String> getModules() {
        Set<String>  modules = new HashSet<>();
        if (null != modulesNode) {
            NodeList nodeList = modulesNode.getChildNodes();
            for (int i = 0; i < nodeList.getLength(); ++i) {
                Node node = nodeList.item(i);
                if (node.getNodeName().equals("module")) {
                    modules.add(node.getTextContent());
                }
            }
        }
        return modules;
    }

    public void setModules(Set<String> modules) {
        if (null != rootDoc) {
            if (null == modulesNode) {
                Node beforeNode = findModulesInsertNode(rootDoc.getDocumentElement());
                modulesNode = insertNode(getRootNode(), beforeNode, "modules");
            } else {
                NodeList nodeList = modulesNode.getChildNodes();
                for (int i = 0; i < nodeList.getLength(); ++i) {
                    modulesNode.removeChild(nodeList.item(i));
                }
            }

            for (String item: modules) {
                appendNode(modulesNode, "module", item);
            }
        }
    }

    public void addModule(String moduleName) {
        if (null != rootDoc) {
            if (null == modulesNode) {
                Node beforeNode = findModulesInsertNode(rootDoc.getDocumentElement());
                modulesNode = insertNode(getRootNode(), beforeNode, "modules");
            }
            appendNode(modulesNode, "module", moduleName);
        }
    }

    private void initData() {
        Element root = rootDoc.getDocumentElement();
        NodeList nodeList = root.getChildNodes();

        for (int i = 0; i < nodeList.getLength(); ++i) {
            Node node = nodeList.item(i);
            if (node.getNodeName().equals("parent")) {
                this.parentNode = node;
            } else if (node.getNodeName().equals("modules")) {
                this.modulesNode = node;
            } else if (node.getNodeName().equals("properties")) {
                this.properties = node;
            }
        }
    }

    private void updateNodeValue(Node root, String key, String val) {
        if (!XmlUtil.updateNodeTextValue(root, key, val)) {
            appendNode(root, key, val);
        }
    }

    private Node getRootNode() {
        return rootDoc == null ? null : rootDoc.getDocumentElement();
    }

    public void save() {
        this.save(this.filePath);
    }

    public void save(String filePath) {
        TransformerFactory transFactory = TransformerFactory.newInstance();
        try {
            Transformer transFormer = transFactory.newTransformer();
            // 美化缩进
            transFormer.setOutputProperty(OutputKeys.INDENT, "yes");
            transFormer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

            this.normalizeGroupId();
            removeBlackBlank();

            // 设置输出结果
            DOMSource domSource = new DOMSource(rootDoc);

            // 生成xml文件
            File file = new File(filePath);

            // 推断是否存在,假设不存在,则创建
            if (!file.exists()) {
                file.createNewFile();
            }

            // 文件输出流
            FileOutputStream out1 = new FileOutputStream(file);

            // 设置输入源
            StreamResult xmlResult = new StreamResult(out1);

            // 输出xml文件
            transFormer.transform(domSource, xmlResult);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
